const KeepAlive = {
  name: "KeepAlive",
  /* 用来标识这是keepalive组件 */
  _isKeepAlive: true,
  setup(props, { slots }) {
    /**
     * 创建一个缓存对象
     * key: vnode.type
     * value: vnode
     *  */
    const cache = new Map();

    /* 当前KeepAlive组件实例 */
    const instance = currentInstance;

    /**
     * 对于 KeepAlive 组件来说，它的实例对象上存在特殊的 keepAliveCtx 对象，该对象由渲染器注入，
     * 该对象会暴露渲染器的一些内部方法，其中 move 函数用来将一段 DOM 移动到另一个容器中。
     */
    const { move, createElement } = instance.keepAliveCtx;

    /* 创建隐藏容器 */
    const storageContainer = createElement("div");

    /**
     * keepAlive组件实例对象上会被添加两个内部函数，分别是_deActivate 和 _activate，
     * 这两个函数会在渲染器中被调用
     */

    /* 将被keepalive的组件移动到隐藏容器中 */
    instance._deActivate = (vnode) => {
      move(vnode, storageContainer);
    };

    /* 将被keepalive的组件从隐藏容器中启动到页面中去 */
    instance._activate = (vnode, container, anchor) => {
      move(vnode, container, anchor);
    };

    /* render渲染函数 */
    return () => {
      /* KeepAlive组件默认插槽中的内容就是要被keepalive的组件 */
      const rawVnode = slots.default();

      /* 判断默认插槽中的内容是否为组件，如果不是组件则直接渲染，因为非组件的虚拟节点无法被KeepAlive */
      if (
        typeof rawVnode.type !== "object" ||
        typeof rawVnode.type !== "function"
      ) {
        return rawVnode;
      }

      /* 在挂载时，先获取缓存中的组件 vnode */
      const cachedVnode = cache.get(rawVnode.type);

      /* 判断缓存中是否存在当前的组件 vnode */
      if (cachedVnode) {
        /* 如果缓存中存在，则不应该执行挂载，而应该执行激活 */
        /* 继承组件实例 */
        rawVnode.component = cachedVnode.component;
        /* 在 vnode 上添加keptAlive属性，标记为true， 表示该组件是被keepalive的组件，避免渲染器重新挂载它 */
        vnode.keptAlive = true;
      } else {
        cache.set(rawVnode.type, rawVnode);
      }

      /* 在组件 vnode 上添加 shouldKeepAlive 属性，并标记为true，避免渲染器真的将组件卸载 */
      rawVnode.shouldKeepAlive = true;
      /* 将 KeepAlive 组件的实例也添加到 vnode 上， 以便在渲染器中访问 （因为Instance在setup函数执行完毕后就被重新赋值为null了，所以需要保存一份）*/
      rawVnode.keepAliveInstance = instance;

      return rawVnode;
    };
  },
};

export default KeepAlive;
